﻿#region Using References

using System;
using System.Diagnostics.Contracts;
using System.Drawing;

#endregion

namespace Beaker.OpenCube.Drawing
{
	public struct HsvColor
		: IEquatable<HsvColor>, IEquatable<Color>
	{
		private readonly float _hue;
		private readonly float _saturation;
		private readonly float _value;
		private readonly float _alpha;

		public HsvColor( float hue, float saturation, float value, float alpha = 1f )
		{
			Contract.Requires<ArgumentOutOfRangeException>( saturation >= 0f );
			Contract.Requires<ArgumentOutOfRangeException>( saturation <= 1f );
			Contract.Requires<ArgumentOutOfRangeException>( value >= 0f );
			Contract.Requires<ArgumentOutOfRangeException>( value <= 1f );
			Contract.Requires<ArgumentOutOfRangeException>( alpha >= 0f );
			Contract.Requires<ArgumentOutOfRangeException>( alpha <= 1f );
			Contract.Ensures( Contract.ValueAtReturn( out _hue ) >= 0f );
			Contract.Ensures( Contract.ValueAtReturn( out  _hue ) < 360f );
			Contract.Ensures( Contract.ValueAtReturn( out _saturation ) >= 0f );
			Contract.Ensures( Contract.ValueAtReturn( out _saturation ) <= 1f );
			Contract.Ensures( Contract.ValueAtReturn( out _value ) >= 0f );
			Contract.Ensures( Contract.ValueAtReturn( out _value ) <= 1f );
			Contract.Ensures( Contract.ValueAtReturn( out _alpha ) >= 0f );
			Contract.Ensures( Contract.ValueAtReturn( out _alpha ) <= 1f );

			_hue = hue % 360f;
			_saturation = saturation;
			_value = value;
			_alpha = alpha;
		}

		[ContractInvariantMethod]
		private void ContractInvariant()
		{
			Contract.Invariant( _hue >= 0f );
			Contract.Invariant( _hue < 360f );
			Contract.Invariant( _saturation >= 0f );
			Contract.Invariant( _saturation <= 1f );
			Contract.Invariant( _value >= 0f );
			Contract.Invariant( _value <= 1f );
			Contract.Invariant( _alpha >= 0f );
			Contract.Invariant( _alpha <= 1f );
		}

		/// <summary>
		/// Gets the hue component of the color.
		/// </summary>
		/// <value>
		/// The hue component of the color.
		/// </value>
		public float Hue
		{
			get
			{
				Contract.Ensures( Contract.Result<float>() >= 0f );
				Contract.Ensures( Contract.Result<float>() < 360f );
				return _hue;
			}
		}

		/// <summary>
		/// Gets the saturation component of the color.
		/// </summary>
		/// <value>
		/// The saturation component of the color.
		/// </value>
		public float Saturation
		{
			get
			{
				Contract.Ensures( Contract.Result<float>() >= 0f );
				Contract.Ensures( Contract.Result<float>() <= 1f );
				return _saturation;
			}
		}

		/// <summary>
		/// Gets the value component of the color.
		/// </summary>
		/// <value>
		/// The value component of the color.
		/// </value>
		public float Value
		{
			get
			{
				Contract.Ensures( Contract.Result<float>() >= 0f );
				Contract.Ensures( Contract.Result<float>() <= 1f );
				return _value;
			}
		}

		/// <summary>
		/// Gets the alpha (transparency) component of the color.
		/// </summary>
		/// <value>
		/// The alpha (transparency) component of the color.
		/// </value>
		public float Alpha
		{
			get
			{
				Contract.Ensures( Contract.Result<float>() >= 0f );
				Contract.Ensures( Contract.Result<float>() <= 1f );
				return _alpha;
			}
		}

		/// <summary>
		/// Adjusts the hue and returns the new colour.
		/// </summary>
		/// <param name="newHue">The new hue.</param>
		/// <returns>The new colour resulting from the adjustment.</returns>
		public HsvColor AdjustHue( float newHue )
		{
			return new HsvColor( newHue, _saturation, _value, _alpha );
		}

		/// <summary>
		/// Adjusts the saturation and returns the new colour.
		/// </summary>
		/// <param name="newSaturation">The new saturation.</param>
		/// <returns>The new colour resulting from the adjustment.</returns>
		public HsvColor AdjustSaturation( float newSaturation )
		{
			Contract.Requires<ArgumentOutOfRangeException>( newSaturation >= 0f );
			Contract.Requires<ArgumentOutOfRangeException>( newSaturation <= 1f );

			return new HsvColor( _hue, newSaturation, _value, _alpha );
		}

		/// <summary>
		/// Adjusts the value and returns the new colour.
		/// </summary>
		/// <param name="newValue">The new value.</param>
		/// <returns>The new colour resulting from the adjustment.</returns>
		public HsvColor AdjustValue( float newValue )
		{
			Contract.Requires<ArgumentOutOfRangeException>( newValue >= 0f );
			Contract.Requires<ArgumentOutOfRangeException>( newValue <= 1f );

			return new HsvColor( _hue, _saturation, newValue, _alpha );
		}

		/// <summary>
		/// Adjusts the alpha and returns the new colour.
		/// </summary>
		/// <param name="newAlpha">The new value.</param>
		/// <returns>The new colour resulting from the adjustment.</returns>
		public HsvColor AdjustAlpha( float newAlpha )
		{
			Contract.Requires<ArgumentOutOfRangeException>( newAlpha >= 0f );
			Contract.Requires<ArgumentOutOfRangeException>( newAlpha <= 1f );

			return new HsvColor( _hue, _saturation, _value, newAlpha );
		}

		#region Mixing

		/// <summary>
		/// Mixes this colour with the given colour.
		/// </summary>
		/// <param name="other">The other colour to mix with.</param>
		/// <returns>
		/// A new colour that is a mixed combination of the two others.
		/// </returns>
		public HsvColor Mix( HsvColor other )
		{
			return
				new HsvColor(
					( _hue + other._hue ) / 2f,
					( _saturation + other._saturation ) / 2f,
					( _value + other._value ) / 2f,
					( _alpha + other._alpha ) / 2f
				);
		}

		/// <summary>
		/// Mixes the given colours to create a new combined colour.
		/// </summary>
		/// <param name="first">The first colour.</param>
		/// <param name="second">The second color.</param>
		/// <returns>
		/// A new colour that is a mixed combination of the two others.
		/// </returns>
		public static HsvColor Mix( HsvColor first, HsvColor second )
		{
			return first.Mix( second );
		}

		#endregion

		#region Comparison

		public override bool Equals( object obj )
		{
			if( ReferenceEquals( null, obj ) ) return false;
			return obj is HsvColor && Equals( (HsvColor)obj );
		}

		public bool Equals( HsvColor other )
		{
			// we considere a color equals if the conversion to RGB results in the same color
			// this is done to avoid the slight rounding differences with floats.
			Color thisRgb = ToRgbColor();
			Color otherRgb = other.ToRgbColor();

			return thisRgb.Equals( otherRgb );
		}

		public bool Equals( Color other )
		{
			// we considere a color equals if the conversion to RGB results in the same color
			// this is done to avoid the slight rounding differences with floats.
			Color thisRgb = ToRgbColor();
			return thisRgb.Equals( other );
		}

		public override int GetHashCode()
		{
			unchecked
			{
				int hashCode = _hue.GetHashCode();
				hashCode = ( hashCode * 397 ) ^ _saturation.GetHashCode();
				hashCode = ( hashCode * 397 ) ^ _value.GetHashCode();
				return hashCode;
			}
		}

		public static bool operator ==( HsvColor left, HsvColor right )
		{
			return left.Equals( right );
		}

		public static bool operator ==( HsvColor left, Color right )
		{
			return left.Equals( right );
		}

		public static bool operator !=( HsvColor left, HsvColor right )
		{
			return !left.Equals( right );
		}

		public static bool operator !=( HsvColor left, Color right )
		{
			return !left.Equals( right );
		}

		#endregion
		#region Conversion

		/// <summary>
		/// Convert this HSV color to an RGB color
		/// </summary>
		public Color ToRgbColor()
		{
			int sector = (int)( Math.Floor( _hue / 60f ) ) % 6;
			double offset = _hue / 60 - Math.Floor( _hue / 60f );

			float value = _value * 255;
			var a = (int)( _alpha * 255 );
			var v = (int)value;
			var p = (int)( value * ( 1f - _saturation ) );
			var q = (int)( value * ( 1f - offset * _saturation ) );
			var t = (int)( value * ( 1f - ( 1f - offset ) * _saturation ) );

			switch( sector )
			{
				case 0:
				default:
					return Color.FromArgb( a, v, t, p );
				case 1:
					return Color.FromArgb( a, q, v, p );
				case 2:
					return Color.FromArgb( a, p, v, t );
				case 3:
					return Color.FromArgb( a, p, q, v );
				case 4:
					return Color.FromArgb( a, t, p, v );
				case 5:
					return Color.FromArgb( a, v, p, q );
			}
		}

		#endregion
	}

	public static class ColorExtensions
	{
		/// <summary>
		/// Converts the RGB color to a HSV color
		/// </summary>
		/// <param name="color">The RGB color to convert to HSV.</param>
		/// <returns>The HSV equivalent of the input RGB color.</returns>
		public static HsvColor ToHsvColor( this Color color )
		{
			int max = Math.Max( color.R, Math.Max( color.G, color.B ) );
			int min = Math.Min( color.R, Math.Min( color.G, color.B ) );

			float alpha = color.A / 255f;
			float hue = color.GetHue();
			float saturation = ( max == 0 ) ? 0 : 1f - ( 1f * min / max );
			float value = max / 255f;

			return new HsvColor( hue, saturation, value, alpha );
		}
	}
}
